package com.hunter.lucene.util.config;

import java.io.IOException;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gh.lucene.Lucenes;
import com.hunter.lucene.util.config.validation.ConfigInfoInvalideException;
import com.hunter.lucene.util.config.validation.ConfigInfoValidator;
import com.hunter.lucene.util.index.Writable;
import com.hunter.lucene.util.index.Writer;
import com.hunter.lucene.util.search.SearchWorker;
import com.hunter.lucene.util.search.Searchable;
import com.hunter.lucene.util.search.Searcher;
import com.hunter.lucene.util.search.SuggestionConsult;
import com.hunter.lucene.util.search.build.NotLazyLoadObjectsBuilder;
import com.hunter.lucene.util.search.build.ObjectsBuilder;

/**
 * 配置类。管理配置的类。是程序的入口。用户最好将此类引用。 与两个类关联。一个是 Writer,另一个是 Searcher。
 * 
 * 当Writer 做过任何修改，则之前打开的 Searcher 应该重新通过此类获得。
 * 
 * @author bastengao
 * 
 */
public class Configuration {
	private static final Logger logger = LoggerFactory
			.getLogger(Configuration.class);
	public static final String DEFAULT_CONFIG_PATH = "hunter.xml";

	private Object safeGuard = new Object() {
		@Override
		public void finalize() throws Exception {
			closeSearcher();

			closeWriter();
		}
	};
	private Directory directory;
	private Injectable<Directory> directoryInjectable = new Injectable<Directory>() {

		@Override
		public void inject(Directory t) {
			logger.debug(t.toString());
			directory = t;
		}
	};
	private AnalyzerHolder analyzers = new AnalyzerHolder();
	private MappingConfigInfoHolder mappingConfigInfos = new MappingConfigInfoHolder();

	private Writable writer = null;
	private Searchable searcher = null;
	private SuggestionConsult consult = null;
	private IndexReader indexReader = null;

	private boolean defaultCreate = false;
	private boolean parameteredCreate = false;
	private boolean create = false;

	/**
	 * No instantiation allowed.避免用户直接构造此类。
	 * 
	 * @throws ConfigLoadException
	 * @throws ConfigInfoInvalideException
	 */
	protected Configuration() throws ConfigLoadException,
			ConfigInfoInvalideException {
		this(DEFAULT_CONFIG_PATH);
	}

	/**
	 * No instantiation allowed.避免用户直接构造此类。
	 * 
	 * @throws ConfigLoadException
	 * @throws ConfigInfoInvalideException
	 */
	protected Configuration(String configPath) throws ConfigLoadException,
			ConfigInfoInvalideException {
		safeGuard.toString();
		init(configPath);
		validate();
	}

	private void init(String configPath) throws ConfigLoadException {
		logger.debug("init {}", configPath);

		// 利用ConfigLoader 对配置文件进行加载。
		ConfigLoader configLoader = new XmlConfigLoader();
		configLoader.getAnalyzerInjector().setInjectable(analyzers);
		configLoader.getDefaultAnalyzerNameInjector().setInjectable(
				analyzers.getDefaultAnalyzerNameInjectable());
		configLoader.getDirectoryInjector().setInjectable(directoryInjectable);
		configLoader.getDocumentConfigInfoInjector().setInjectable(
				mappingConfigInfos.getDocumentConfigs());
		configLoader.getUndocumentConfigInfoInjector().setInjectable(
				mappingConfigInfos.getUndocumentConfigs());

		configLoader.load(configPath);
	}

	/**
	 * 验证配置信息。 进行以下验证：
	 * <ul>
	 * <li>默认的分词器，必须存在。</li>
	 * <li>每个Document 指定的分词器必须存在。</li>
	 * <li>每个Document 的 Discriminator 必须不同</li>
	 * <li>Document 中的所有 Field 和Discriminator(特殊的Field)的 name 必须不同</li>
	 * <li>每个 Undocument 中的所有 Discriminator 必须不同</li>
	 * </ul>
	 * 
	 * @throws ConfigInfoInvalideException
	 */
	private void validate() throws ConfigInfoInvalideException {
		ConfigInfoValidator configInfoValidator = new ConfigInfoValidator(
				analyzers, mappingConfigInfos);
		configInfoValidator.validate();
	}

	/**
	 * 根据默认的配置文件,创建 Configuration.
	 * 
	 * @return
	 * @throws ConfigLoadException
	 * @throws ConfigInfoInvalideException
	 */
	public static Configuration build() throws ConfigLoadException,
			ConfigInfoInvalideException {
		return new Configuration();
	}

	/**
	 * 根据指定的配置文件，创建Configuration.
	 * 
	 * @param configPath
	 * @return
	 * @throws ConfigLoadException
	 * @throws ConfigInfoInvalideException
	 */
	public static Configuration build(String configPath)
			throws ConfigLoadException, ConfigInfoInvalideException {
		if (configPath == null) {
			throw new NullPointerException("configPath is null.");
		}
		return new Configuration(configPath);
	}

	/**
	 * 设置directory。指定索引的存放 Directory。 此Directory 将被 searhcer 和 writer 使用。
	 * 
	 * @param directory
	 */
	public void setDirectory(Directory directory) {
		this.directory = directory;
	}

	/**
	 * 添加被注解的类
	 * 
	 * @param documentableClasses
	 */
	public void addCapability(Class<?>... documentableClasses) {
		throw new UnsupportedOperationException();
	}

	/**
	 * 删除被注解类在此类上的作用
	 * 
	 * @param documentableClasses
	 */
	public void removeCapability(Class<?>... documentableClasses) {
		throw new UnsupportedOperationException();
	}

	/**
	 * 绑定一个Analyzer。默认的Analyzer 的 name 为 "default"
	 * 
	 * @param name
	 * @param analyzer
	 */
	public void bindAnalyzer(String name, Analyzer analyzer) {
		throw new UnsupportedOperationException();
	}

	/**
	 * 解除绑定在 name 上的 analyzer.
	 * 
	 * @param name
	 */
	public void unbindAnalyzer(String name) {
		throw new UnsupportedOperationException();
	}

	/**
	 * 如果有，则直接返回。如果没有，那么创建Writer。创建时使用默认方式，如果索引不存在则创建，如果存在则追加。并返回。
	 * 
	 * @return
	 * @throws LuceneException
	 */
	public Writable getOrBuildWriter() throws LuceneException {
		if (writer == null) {
			synchronized (this) {
				if (writer == null) {
					defaultCreate = true;
					parameteredCreate = false;
					buildWriter();
				}
			}
		} else {
			if (!defaultCreate) {
				synchronized (this) {
					if (!defaultCreate) {
						defaultCreate = true;
						parameteredCreate = false;
						buildWriter();
					}
				}
			}
		}
		logger.debug("getWriter");
		return writer;
	}

	/**
	 * 如果有，则直接返回。如果没有，那么创建 Writer。创建时使用 指定的 create 方式。
	 * 
	 * @param create
	 * @return
	 * @throws LuceneException
	 */
	public Writable getOrBuildWriter(boolean create) throws LuceneException {
		if (writer == null) {
			synchronized (this) {
				if (writer == null) {
					defaultCreate = false;
					parameteredCreate = true;
					this.create = create;
					buildWriter(create);
				}
			}
		} else {
			if (!parameteredCreate) {
				if (this.create != create) {
					synchronized (this) {
						if (!parameteredCreate && this.create != create) {
							defaultCreate = false;
							parameteredCreate = true;
							this.create = create;
							buildWriter(create);
						}
					}
				}

			}
		}
		logger.debug("getWriter");
		return writer;
	}

	public Writable buildWriter() throws LuceneException {
		logger.debug("buildWriter");
		closeWriter();
		Analyzer defaultAnalyzer = analyzers.get(analyzers
				.getDefaultAnalyzerName());
		IndexWriter indexWriter = null;
		try {
			indexWriter = Lucenes.createIndexWriter(directory, defaultAnalyzer);
		} catch (CorruptIndexException e) {
			throw new LuceneException(e);
		} catch (LockObtainFailedException e) {
			throw new LuceneException(e);
		} catch (IOException e) {
			throw new LuceneException(e);
		}
		writer = new Writer(indexWriter, analyzers, mappingConfigInfos
				.getDocumentConfigs());

		return writer;
	}

	/**
	 * 创建 Writer，使用指定的 create 方式。
	 * 
	 * @param create
	 *            如果为true，重新创建；如果为 false ，追加。
	 * @return
	 * @throws LuceneException
	 */
	public Writable buildWriter(boolean create) throws LuceneException {
		closeWriter();
		logger.debug("create :{}", create);
		Analyzer defaultAnalyzer = analyzers.get(analyzers
				.getDefaultAnalyzerName());
		IndexWriter indexWriter = null;
		try {
			indexWriter = Lucenes.createIndexWriter(directory, defaultAnalyzer,
					create);
		} catch (CorruptIndexException e) {
			throw new LuceneException(e);
		} catch (LockObtainFailedException e) {
			throw new LuceneException(e);
		} catch (IOException e) {
			throw new LuceneException(e);
		}
		writer = new Writer(indexWriter, analyzers, mappingConfigInfos
				.getDocumentConfigs());
		return writer;
	}

	private void closeWriter() throws LuceneException {
		if (writer != null) {
			try {
				writer.close();
				writer = null;
			} catch (IOException e) {
				throw new LuceneException(e);
			}
		}
	}

	/**
	 * 如果有，则直接返回。如果没有，那么创建Searcher，并返回。
	 * 
	 * @return
	 * @throws LuceneException
	 */
	public Searchable getOrBuildSearcher() throws LuceneException {
		if (searcher == null) {
			searcher = rebuildSearhcer();
		}
		logger.debug("get");
		return searcher;
	}

	/**
	 * 重新创建Searcher。此方法返回一个最新的Searcher。通常此方法在Writer 修改index 后调用，可以得到对index
	 * 修改可见的Searcher，否则 Searcher 只对 Searcher 创建时的index 状态可见。
	 * 
	 * @return
	 * @throws LuceneException
	 */
	public Searchable rebuildSearhcer() throws LuceneException {

		logger.debug("buildSearcher");
		try {
			indexReader = IndexReader.open(directory, true);
		} catch (CorruptIndexException e) {
			throw new LuceneException(e);
		} catch (IOException e) {
			throw new LuceneException(e);
		}

		// TODO ObectsBuilder should be reused ,this method is just to
		// reopen index for searcher.
		ObjectsBuilder builder = new NotLazyLoadObjectsBuilder(
				mappingConfigInfos.getUndocumentConfigs().values(), indexReader);

		IndexSearcher indexSearcher;
		try {
			indexSearcher = new IndexSearcher(directory);
		} catch (CorruptIndexException e) {
			throw new LuceneException(e);
		} catch (IOException e) {
			throw new LuceneException(e);
		}
		SearchWorker worker = new SearchWorker(indexSearcher);
		searcher = new Searcher(worker, builder);

		return searcher;
	}

	private void closeSearcher() throws IOException {
		if (searcher != null) {
			searcher.close();
		}
	}

	/**
	 * 创建SuggestionConsult
	 * 
	 * @param fieldName
	 * @return
	 * @throws LuceneException
	 */
	public SuggestionConsult buildSuggestionConsult(String fieldName)
			throws LuceneException {
		consult = new SuggestionConsult(directory, fieldName);
		return consult;
	}

	/**
	 * 获取SuggestionConsult。如果不存在，则根据指定的fieldName创建SuggestionConsult并返回。
	 * @return
	 * @throws LuceneException
	 */
	public SuggestionConsult getSuggestionConsult(String fieldName)
			throws LuceneException {
		if (consult == null) {
			consult = new SuggestionConsult(directory, fieldName);
		}
		return consult;
	}

	/**
	 * 刷新SuggestionConsult。当索引发生改变的时候，通常是Writer改变了索引后，调用此方法与最新的索引同步。
	 * @return
	 * @throws LuceneException
	 */
	public SuggestionConsult refreshSuggestionConsult() throws LuceneException {
		consult.reinit();
		return consult;
	}

	private void closeIndexReader() throws IOException {
		indexReader.close();
	}

	/**
	 * 关闭
	 * 
	 * @throws IOException
	 * @throws LuceneException
	 */
	public void close() throws IOException, LuceneException {
		try {
			closeIndexReader();
		} finally {
			try {
				closeSearcher();
			} finally {
				closeWriter();
			}
		}

	}
}
